﻿#region Imports
using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.Linq;
using CommonUtilities.Enums;
using CommonUtilities.Exceptions;
using CommonUtilities.Messages;
using CommonUtilities.Properties;
using CommonUtilities.Util;
#endregion

namespace CommonUtilities.Extensions
{
    /// <summary>
    /// Contains extension methods for Enums
    /// </summary>
    /// <seealso cref="http://stackoverflow.com/questions/1415140/can-my-enums-have-friendly-names" />
    public static class EnumExtensions
    {
        #region Extension methods
        public static IEnumerable<TEnum> _GetValues<TEnum>(this TEnum value)
            where TEnum : struct //, IComparable, IConvertible, IFormattable
        {
            if (!new TEnum().IsType<Enum>(eMatchType.Hierarchy))
                throw new InvalidOperationException(Resources.Extn_Enum_NotEnumError.FormatTextFromParams(typeof(TEnum)));

            //return (typeof(TEnum)).GetEnumValues().Cast<TEnum>();
            return Enum.GetValues(typeof(TEnum)).Cast<TEnum>();
        }

        public static IEnumerable<TEnum> _ToEnumerable<TEnum>(this TEnum value)
            where TEnum : struct //, IComparable, IConvertible, IFormattable
        {
            if (!new TEnum().IsType<Enum>(eMatchType.Hierarchy))
                throw new InvalidOperationException(Resources.Extn_Enum_NotEnumError.FormatTextFromParams(typeof(TEnum)));

            return value._GetValues();
        }

        public static List<TEnum> _ToList<TEnum>(this TEnum value)
           where TEnum : struct //, IComparable, IConvertible, IFormattable
        {
            if (!new TEnum().IsType<Enum>(eMatchType.Hierarchy))
                throw new InvalidOperationException(Resources.Extn_Enum_NotEnumError.FormatTextFromParams(typeof(TEnum)));

            return value._ToEnumerable().ToList();
        }

        // RKD - test
        public static bool _TryParse<TEnum>(this TEnum value, string strType, out TEnum result)
            where TEnum : struct //, IComparable, IConvertible, IFormattable
        {
            if (!new TEnum().IsType<Enum>(eMatchType.Hierarchy))
                throw new InvalidOperationException(Resources.Extn_Enum_NotEnumError.FormatTextFromParams(typeof(TEnum)));

            string strTypeFixed = strType.Replace(' ', '_');
            if (Enum.IsDefined(typeof(TEnum), strTypeFixed))
            {
                result = (TEnum)Enum.Parse(typeof(TEnum), strTypeFixed, true);
                return true;
            }
            else
            {
                foreach (string sValue in Enum.GetNames(typeof(TEnum)))
                {
                    if (sValue.Equals(strTypeFixed,
                        StringComparison.OrdinalIgnoreCase))
                    {
                        result = (TEnum)Enum.Parse(typeof(TEnum), sValue);
                        return true;
                    }
                }

                result = default(TEnum);
                return false;
            }
        }

        // isSortAscending : NULL (design-time order) / TRUE (sort asc) / FALSE (sort desc)
        public static void _ForEachEnum<TEnum>(this TEnum value, Action<TEnum> action, bool? isSortAscending = null)
            where TEnum : struct //, IComparable, IConvertible, IFormattable
        {
            if (!new TEnum().IsType<Enum>(eMatchType.Hierarchy))
                throw new InvalidOperationException(Resources.Extn_Enum_NotEnumError.FormatTextFromParams(typeof(TEnum)));

            if (isSortAscending.HasValue)
                if (isSortAscending.Value)
                    MessageBoxUtil.Show(true, "[ isSortAscending = true ]\nsorts all records, irrespective of default item which is to be at first.");

            if (isSortAscending.IsNull()) value._GetValues()._ForEach(action);
            else value._GetValues().OrderByDirection(val => val.ToString(), isSortAscending.Value)._ForEach(action);
        }

        // RKD - test
        public static TEnum _Convert<TEnum>(this TEnum e)
            where TEnum : struct //, IComparable, IConvertible, IFormattable
        {
            if (!new TEnum().IsType<Enum>(eMatchType.Hierarchy))
                throw new InvalidOperationException(Resources.Extn_Enum_NotEnumError.FormatTextFromParams(typeof(TEnum)));

            object o = null;
            Type type = typeof(TEnum);

            if (type == typeof(int))
                o = System.Convert.ToInt32(e);
            else if (type == typeof(long))
                o = System.Convert.ToInt64(e);
            else if (type == typeof(short))
                o = System.Convert.ToInt16(e);
            else
                o = System.Convert.ToString(e);

            return (TEnum)o;
        }

        // RKD - test
        public static TEnum _ConvertByName<TEnum>(this TEnum value)
            where TEnum : struct //, IComparable, IConvertible, IFormattable
        {
            if (!new TEnum().IsType<Enum>(eMatchType.Hierarchy))
                throw new InvalidOperationException(Resources.Extn_Enum_NotEnumError.FormatTextFromParams(typeof(TEnum)));

            return (TEnum)Enum.Parse(typeof(TEnum), Enum.GetName(value.GetType(), value));
        }

        // RKD - test
        public static TEnum _ConvertByValue<TEnum>(this TEnum value)
            where TEnum : struct //, IComparable, IConvertible, IFormattable
        {
            if (!new TEnum().IsType<Enum>(eMatchType.Hierarchy))
                throw new InvalidOperationException(Resources.Extn_Enum_NotEnumError.FormatTextFromParams(typeof(TEnum)));

            return (TEnum)((dynamic)((int)((object)value)));
        }

        public static bool Contains<TEnum>(this TEnum keys, TEnum flag)
            where TEnum : struct //, IComparable, IConvertible, IFormattable
        {
            if (!new TEnum().IsType<Enum>(eMatchType.Hierarchy))
                throw new InvalidOperationException(Resources.Extn_Enum_NotEnumError.FormatTextFromParams(typeof(TEnum)));

            ulong keysVal = Convert.ToUInt64(keys);
            ulong flagVal = Convert.ToUInt64(flag);

            return ((keysVal & flagVal) == flagVal);
        }

        // RKD - test
        /// <summary>
        /// Check to see if a flags enumeration has a specific flag set.
        /// </summary>
        /// <param name="variable">Flags enumeration to check</param>
        /// <param name="value">Flag to check for</param>
        /// <returns></returns>
        public static bool _HasFlag<TEnum>(this TEnum variable, TEnum value)
            where TEnum : struct //, IComparable, IConvertible, IFormattable
        {
            if (!new TEnum().IsType<Enum>(eMatchType.Hierarchy))
                throw new InvalidOperationException(Resources.Extn_Enum_NotEnumError.FormatTextFromParams(typeof(TEnum)));

            // Not as good as the .NET 4 version of this function, but should be good enough
            if (!Enum.IsDefined(variable.GetType(), value))
            {
                throw new ArgumentException(string.Format(
                    "Enumeration type mismatch. The flag is of type '{0}', was expecting '{1}'.",
                    value.GetType(), variable.GetType()));
            }

            ulong num = Convert.ToUInt64(value);
            return ((Convert.ToUInt64(variable) & num) == num);
        }

        /// <summary>
        /// Determines whether the specified value has flags. Note this method is up to 60 times faster
        /// than the one that comes with .NET 4 as it avoids any explict boxing or unboxing. 
        /// </summary>
        /// <typeparam name="TEnum">The type of the enum.</typeparam>
        /// <param name="value">The value.</param>
        /// <param name="flags">The flag.</param>
        /// <returns>
        ///  <c>true</c> if the specified value has flags; otherwise, <c>false</c>.
        /// </returns>
        /// <exception cref="ArgumentException">If TEnum is not an enum.</exception>
        public static bool _HasFlags<TEnum>(this TEnum value, eAndOrNone binaryOp, params TEnum[] flags)
            where TEnum : struct, IComparable, IConvertible, IFormattable
        {
            if (!new TEnum().IsType<Enum>(eMatchType.Hierarchy))
                throw new InvalidOperationException(Resources.Extn_Enum_NotEnumError.FormatTextFromParams(typeof(TEnum)));

            bool result = false;
            switch (binaryOp)
            {
                case eAndOrNone.All:
                    result = flags._All(flag => value._HasFlag<TEnum>(flag));
                    break;

                case eAndOrNone.Any:
                    result = flags._Any(flag => value._HasFlag<TEnum>(flag));
                    break;

                case eAndOrNone.None:
                    result = flags._None(flag => value._HasFlag<TEnum>(flag));
                    break;

                default:
                    break;
            }

            return result;

            //bool result = (binaryOp == eAndOr.And);
            //foreach (TEnum flag in flags)
            //{
            //    if (binaryOp == eAndOr.Or)
            //    {
            //        result = result || value._HasFlag<TEnum>(flag);
            //        if (result) break;
            //    }
            //    else if (binaryOp == eAndOr.And)
            //    {
            //        result = result && value._HasFlag<TEnum>(flag);
            //        if (!result) break;
            //    }
            //}
            //
            //return result;


            //return EnumExtensionsInternal<TEnum>.HasFlagsDelegate(value, flag);
        }

        /*
        #region Nested Classes 

        static class EnumExtensionsInternal<TEnum> where TEnum : struct, IComparable, IConvertible, IFormattable
        {
        #region Public Static Variables 
        /// <summary>
        /// The delegate which determines if a flag is set.
        /// </summary>
        public static readonly Func<TEnum, TEnum, bool> HasFlagsDelegate = CreateHasFlagDelegate();
        #endregion Public Static Variables 

        #region Private Static Methods 
        /// <summary>
        /// Creates the has flag delegate.
        /// </summary>
        /// <returns></returns>
        private static Func<TEnum, TEnum, bool> CreateHasFlagDelegate()
        {
        if(!typeof(TEnum).IsEnum)
        {
            throw new ArgumentException(string.Format("{0} is not an Enum", typeof(TEnum)), typeof(EnumExtensionsInternal<>).GetGenericArguments()[0].Name);
        }
        ParameterExpression valueExpression = Expression.Parameter(typeof(TEnum));
        ParameterExpression flagExpression = Expression.Parameter(typeof(TEnum));
        ParameterExpression flagValueVariable = Expression.Variable(Type.GetTypeCode(typeof(TEnum)) == TypeCode.UInt64 ? typeof(ulong) : typeof(long));
        Expression<Func<TEnum, TEnum, bool>> lambdaExpression = Expression.Lambda<Func<TEnum, TEnum, bool>>(
            Expression.Block(
            new[] { flagValueVariable }, 
            Expression.Assign(
                flagValueVariable, 
                Expression.Convert(
                flagExpression, 
                flagValueVariable.Type
                )
            ), 
            Expression.Equal(
                Expression.And(
                Expression.Convert(
                    valueExpression, 
                    flagValueVariable.Type
                ), 
                flagValueVariable
                ), 
                flagValueVariable
            )
            ), 
            valueExpression, 
            flagExpression
        );
        return lambdaExpression.Compile();
        }
        #endregion Private Static Methods 
        }
        /// <summary>
        /// ////////////////////////////////////////////////
        /// </summary>
        /// <returns></returns>
        private static Func<TEnum, TEnum, bool> CreateHasFlagDelegate2()
        {
            if(!typeof(TEnum).IsEnum)
            {
                throw new ArgumentException(string.Format("{0} is not an Enum", typeof(TEnum)), typeof(EnumExtensionsInternal<>).GetGenericArguments()[0].Name);
            }
            ParameterExpression valueExpression = Expression.Parameter(
                    typeof(TEnum), 
                    typeof(TEnum).Name
            );
            ParameterExpression flagExpression = Expression.Parameter(
                    typeof(TEnum), 
                    typeof(TEnum).Name
            );
            var targetType = Type.GetTypeCode(typeof(TEnum)) == TypeCode.UInt64 ? typeof(ulong) : typeof(long);
            Expression<Func<TEnum, TEnum, bool>> lambdaExpression = Expression.Lambda<Func<TEnum, TEnum, bool>>(
                            Expression.Equal(
                                    Expression.And(
                                            Expression.Convert(
                                                    valueExpression, 
                                                    targetType
                                            ), 
                                            Expression.Convert(
                                                flagExpression, 
                                                targetType
                                            )
                                    ), 
                                    Expression.Convert(
                                        flagExpression, 
                                        targetType
                                    )
                            ), 
                    valueExpression, 
                    flagExpression
            );
            return lambdaExpression.Compile();
        }

        this version should compile in .NET 3.5 and if it doesn't I can't understand why.
        share|improve this answer
	
        edited Nov 5 '10 at 21:25

	
        answered Nov 5 '10 at 20:53
        Michael B
        2, 328514
	
        1 	 
	
        Interesting approach, but judging by the methods you are using this is .NET 4. We've already determined that .NET 4 isn't an option. An it has a built in function to do this anyway. –  chilltemp Nov 5 '10 at 21:15
	 
	
        Ah actually everything in here could be done without .Net 4 the only thing is that for minor more efficiency, I save the result of casting the flag as it has to be cast twice. This is not allowed in .Net 3.5, however, you can actually replace the flagValueVariable usage and the assignment:: I'll revise the answer to support .net 3.5. –  Michael B Nov 5 '10 at 21:21
        add comment
        up vote 2 down vote
	

        Unfortunately no there is not a good way to make an extension method like this. In order for this to work you'd need to have a generic method which operated on enum values. Unfortunately there is no way to constrain generic arguments to be an enum

        // Ilegal
        public static bool Contains<T>(this T value, T flag) where T : enum {
          ...
        }

        The best I've come up with is the following

        public static bool HasFlag<T>(this System.Enum e, T flag) 
        {
            var intValue = (int)(object)e;
            var intFlag = (int)(object)flag;
            return (intValue & intFlag) != 0;
        }

            public static bool IsValid<T>(this T value)
        {
            return Enum.IsDefined(value.GetType(), value);
        }

        share|improve this answer
	
        edited Nov 5 '10 at 18:28

	
        answered Nov 5 '10 at 18:22
        Robin Robinson
        97111321
	        add comment
        up vote 1 down vote
	

        I have another approach here that I just cooked up quickly using the fact that Delegate.CreateDelegate allows conversion between methods for Enum's and their underlying types. The following approach is much like my previous answer but I feel might be easier to read for people who don't know expression tree syntax. Basically we know that Enums only have 8 possible underlying types, and so we just create a static method for each call it could use. Since I'm going for brevity I use anonymous methods which happened to be named the same thing as the possible typecode values.This approach will work in .Net 3.5::

        public static class EnumHelper
        {
            delegate bool HasFlag<T>(T left, T right);
            static readonly HasFlag<Byte> Byte = (x, y)=> (x&y) ==y;
            static readonly HasFlag<SByte> Sbyte = (x, y)=> (x&y) ==y;
            static readonly HasFlag<Int16> Int16 = (x, y)=> (x&y) ==y;
            static readonly HasFlag<UInt16> UInt16 = (x, y)=> (x&y) ==y;
            static readonly HasFlag<Int32> Int32 = (x, y)=> (x&y) ==y;
            static readonly HasFlag<UInt32> UInt32 = (x, y)=> (x&y) ==y;
            static readonly HasFlag<Int64> Int64 = (x, y)=> (x&y) ==y;
            static readonly HasFlag<UInt64> UInt64 = (x, y)=> (x&y) ==y;

            public static bool HasFlags<TEnum>(this TEnum @enum, TEnum flag) where TEnum:struct, IConvertible, IComparable, IFormattable
            {
                return Enum<TEnum>.HasFlag(@enum, flag);
            }
            class Enum<TEnum> where TEnum:struct, IConvertible, IComparable, IFormattable
            {
                public static HasFlag<TEnum> HasFlag = CreateDelegate();
                static HasFlag<TEnum> CreateDelegate()
                {
                    if (!typeof(TEnum).IsEnum) throw new ArgumentException(string.Format("{0} is not an enum", typeof(TEnum)), typeof(Enum<>).GetGenericArguments()[0].Name);
                    var delegateName = Type.GetTypeCode(typeof(TEnum)).ToString();
                    var @delegate = typeof(EnumHelper).GetField(delegateName, BindingFlags.Static | BindingFlags.NonPublic).GetValue(null) as Delegate;
                    return Delegate.CreateDelegate(typeof(HasFlag<TEnum>), @delegate.Method) as HasFlag<TEnum>;
                }
            }
        }

        public static bool TryParse<T>(this Enum theEnum, string valueToParse, out T returnValue)
        {
            returnValue = default(T);

            int intEnumValue;
            if (Int32.TryParse(valueToParse, out intEnumValue))
            {
                if (Enum.IsDefined(typeof(T), intEnumValue))
                {
                    returnValue = (T)(object)intEnumValue;

                    return true;
                }
            }

            return false;
        }
        */

        // RKD - test
        public static string GetEnumDescription<TEnum>(this TEnum value)
            where TEnum : struct, IComparable, IConvertible, IFormattable
        {
            Type type = value.GetType(); string name = Enum.GetName(type, value);

            string desc = value.GetEnumAttributeProperty<TEnum, DescriptionAttribute, string>("Description", name);
            if (desc.IsBlank()) desc = name;

            return desc.Coalesce(name);
        }

        // RKD - test
        public static string ToString<TEnum>(this TEnum e)
            where TEnum : struct, IComparable, IConvertible, IFormattable
        {
            if (!new TEnum().IsType<Enum>(eMatchType.Hierarchy))
                throw new InvalidOperationException(Resources.Extn_Enum_NotEnumError.FormatTextFromParams(typeof(TEnum)));

            return "(" + e.GetType().Namespace + ") " + e.GetType().FullName.Replace("+", ".").Replace(e.GetType().Namespace, "").Trim('.') + "." + e.ToString();
        }

        // RKD - test
        //public static Enum EnumFromInt<TEnum>(this Enum e, int value)
        public static TEnum EnumFromInt<TEnum>(int value)
            where TEnum : struct, IComparable, IConvertible, IFormattable
        {
            if (!new TEnum().IsType<Enum>(eMatchType.Hierarchy))
                throw new InvalidOperationException(Resources.Extn_Enum_NotEnumError.FormatTextFromParams(typeof(TEnum)));

            try
            {
                if (true) // !Enum.GetValues(typeof(TEnum)).HasValue(0))
                {
                    //return e.GetType().;
                    //.GetValues(typeof(eCopyProperties)).Cast<eCopyProperties>())

                    return (TEnum)Enum.GetValues(typeof(TEnum)).GetValue(value);
                    //return (Enum)Enum.ToObject(typeof(TEnum), value);
                    // //eExtnCase z = (eExtnCase)0; 
                }
                else
                    throw new DefaultValueNotAvailableException(null);
            }
            catch (Exception ex)
            {
                throw new DefaultValueNotAvailableException(ex);
            }
        }

        // RKD : make generic
        //public static Enum GetDefaultValue(this Enum value)
        public static TEnum GetDefaultValue<TEnum>()
            where TEnum : struct, IComparable, IConvertible, IFormattable
        //    where TEnum : Enumerators
        {
            if (!new TEnum().IsType<Enum>(eMatchType.Hierarchy))
                throw new InvalidOperationException(Resources.Extn_Enum_NotEnumError.FormatTextFromParams(typeof(TEnum)));

            //GetClassAttribute<Enum, DefaultValueAttribute>();

            //return (Enum) (eCopyProperties.Location.);
            // throw new Exceptions.NoDefaultValueAvailableException();
            // return (TEnum)EnumFromInt<TEnum>(0);

            // OUTPUT : TEnum
            Type t = typeof(TEnum);
            DefaultValueAttribute[] attributes = (DefaultValueAttribute[])t.GetCustomAttributes(typeof(DefaultValueAttribute), false);
            if (!attributes.IsNull() && attributes.Length > 0)
                return (TEnum)attributes[0].Value;
            else
                return (TEnum)EnumFromInt<TEnum>(0);

            //// OUTPUT : Enum
            //return default(eExtnCase);

            /*
                public enum Status
                {
                    New = 10, 
                    Old = 20, 
                    Actual = 30, 

                    // Use Status.Default to specify default status in your code. 
                    Default = New
                }
            */
        }
        #endregion

        #region Methods
        public static string ToString()
        {
            return Utilities.ToString(typeof(EnumExtensions));
        }
        #endregion
    }
}
